package taller2.model.graph;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;

import taller2.interfaces.Connection;
import taller2.interfaces.Graph;
import taller2.interfaces.Node;
import taller2.utils.Constants;
import taller2.utils.Point;
import taller2.utils.Utils;


public class GraphImpl implements Serializable,Graph{
	private static final long serialVersionUID = 1L;
	protected ArrayList<Node> nodes;
	protected boolean isDirected;

	// constructor
	public GraphImpl(){
		nodes = new ArrayList<Node>();
	}	
	
	// copy constructor
	public GraphImpl(Graph other){
        nodes = new ArrayList<Node>();
       
        ArrayList<Node> otherNodes = other.getNodes();
       
        for (Node node:otherNodes){
        	Node nuevo = new NodeImpl(node.getPos(),node.getTag());
        	copyNode(node,nuevo);
        	nodes.add(nuevo);
        }
         
		isDirected = other.isDirected();
        
        for(Node node:otherNodes){
                for(Connection connection:((NodeImpl)node).getConnections()){   	
                        int index1 = otherNodes.indexOf(node);
                        int index2 = otherNodes.indexOf(connection.getOtherNode());
                        ConnectNodes(index1,index2,connection.getWeight());

                        Node node1 = getNodes().get(index1);
                        Node node2 = getNodes().get(index2);
                        Connection conn = node1.getConnectionTo(node2);
                        conn.setCapacity(connection.getCapacity());
                        conn.setFlux(connection.getFlux());
                        
                        if(connection.isSelected())
                        	node1.selectConnectionTo(node2);
                }
        }
	}

	// returns true if the graph is directed
	public boolean isDirected() {
		return true;
	}
	
	public boolean savedAsDirected(){
		return  isDirected;
	}
	
	public void setSaveAsDirected(boolean savedAsDirected){
		isDirected = savedAsDirected;
	}

	// replaces the graph for the other one
	public void replace(Graph other) {
		isDirected = other.isDirected();
		nodes = other.getNodes();
	}	
	
	// adds a new node to the graph 
	public void add(Point posNode,String nodeTag){
		Node newNode = new NodeImpl(posNode,nodeTag);
		nodes.add(newNode);
	}
	
	// connects the first with the second Node  (only if they are not already connected this way)
	public void ConnectNodes(int index1,int index2, int weight){
		Node firstNode = nodes.get(index1);
		Node secondNode = nodes.get(index2);
		firstNode.addConnection(secondNode, weight);
	}
	
	// unmarks all nodes as not used
	public void unmarkNodes(){
		for (Node node:nodes)
			node.unmark();
	}
	
	// unselects all nodes 
	public void unselectNodes(){
		for (Node node:nodes)
			node.unselect();
	}

	// unselects all connections
	public void unselectConnections(){
		HashMap<String, Connection> connectionHashMap = getAllConnections();
		for (Connection connection: connectionHashMap.values())
			connection.unselect();
	}
	
	//numbers nodes from 1 onwards in the same order they are transversed when iterating through this.nodes
	public void normalizeNodesTags(){
		Integer tag = 0;	
		for (Integer i = 0; i < nodes.size(); i++){
			tag = i + 1;
			nodes.get(i).setTag(tag.toString());
		}

	}


	// returns the list of nodes
	public ArrayList<Node> getNodes(){
		return nodes;
	}
	
	// returns the position of the node
	public Point getNodePosition(int nodeIndex) {
		return nodes.get(nodeIndex).getPos();
	}	

	// returns the node with the indicated index
	public Node getNodeByIndex(int index){
		if(index < nodes.size())
			return nodes.get(index);
		return null;
	}
	
	// deletes the node with the corresponding index
	public void deleteNode(int nodeIndex){
		Node selectedNode = nodes.get(nodeIndex);
		nodes.remove(selectedNode);
		
		for(Node node:nodes){
			node.deleteConnections(selectedNode);
		}
	}
	
	// deletes the connections from node1(index1) to node2(index2)
	public void deleteConnections(int index1, int index2) {
		Node node1 = nodes.get(index1);
		Node node2 = nodes.get(index2);
		node1.deleteConnections(node2);
	}	
	
	// returns the index of the node or -1 if not found
	public int getNodeIndex(Node selectedNode) {
		return nodes.indexOf(selectedNode);
	}
	
	// deletes all nodes in the graph
	public void deleteAll() {
		nodes = new ArrayList<Node>();
	}	

	// creates a random graph of the requiered amount of vertices and connections
	public void randomGraph(int vert, int conn) {
		deleteAll();
		for(int i=0;i<vert;i++){
			int posX = Utils.randInt(0, 450);
			int posY = Utils.randInt(0, 450);
			nodes.add(new NodeImpl(new Point(posX,posY),String.valueOf(i)));
		}
		
		for(int i=0;i<conn;i++){
			int index,index2,weight;
				index = Utils.randInt(0,vert-1);
				index2 = Utils.randInt(0,vert-1);
			
			weight = Utils.randInt(0,100);
			ConnectNodes(index,index2,weight);
		}
	}	
	
	// saves the graph to a file
	public void save(String path) {
		GraphSerializer graphSer = new GraphSerializer();
		graphSer.save(path, this);
	}

    // loads the graph from the file
	public void load(String path) {
		GraphSerializer graphSer = new GraphSerializer();
		graphSer.load(path, this);
	}

    @Override
    public HashMap<String, Connection> getAllConnections() {
        HashMap<String, Connection> connectionHashMap = new HashMap<>();
        for (Node node : nodes) {
            for (Connection connection : node.getConnections()) {
                connectionHashMap.put(connection.getID(), connection);
            }
        }
        return connectionHashMap;
    }

	public ArrayList<Connection> getConnectionsTo(Node node){
		ArrayList<Connection> list = new ArrayList<Connection>();
		for(Connection connection : getAllConnections().values()){
			if(connection.getOtherNode().equals(node)){
				list.add(connection);
			}	
		}
		return list;
	}
    
    public Node getFirstNode() {
       return nodes.get(0);
    }
        
    class GraphSerializer{
    	// saves the graph to a file
    	public void save(String path, Graph graph) {
    		
    		try {
    			new File(Constants.savedGraphsPath).mkdirs();
    			FileOutputStream fileOut = new FileOutputStream(path);
    			ObjectOutputStream out = new ObjectOutputStream(fileOut);
    			out.writeObject( (GraphImpl) graph);
    	        out.close();
    	        fileOut.close();
    		} catch (IOException | NullPointerException e) {
    			e.printStackTrace();
    		}
    	}

        // loads the graph from the file
    	public void load(String path, Graph graph) {
           
    		FileInputStream fileIn;
            
    		try {
    			fileIn = new FileInputStream(path);
    	        ObjectInputStream in = new ObjectInputStream(fileIn);
    	        Graph loadedGraph = (Graph) in.readObject();
    	        GraphImpl loadedGraphTyped = new GraphImpl(loadedGraph);
    	        
    	        in.close();
    	        fileIn.close();
    	        
    	        graph.replace(loadedGraphTyped);
    	        graph.setSaveAsDirected(loadedGraphTyped.savedAsDirected());
    		} catch (IOException | ClassNotFoundException | NullPointerException e) {
    			//e.printStackTrace();
    		}
    	}
    }

    private void copyNode(Node orig,Node copy){
    	copy.setPos(orig.getPos());
    	copy.setId(orig.getNumberId());
    	copy.setTag(orig.getTag());
    	copy.setMarker(orig.getMarker());			
    	copy.changeColor(orig.getColor());

		if (orig.isSelected())
			copy.select();
  	
    	if (orig.isMarked())
    		copy.mark();    	
    }
    
    @Override
    public void killWeights(){
    	HashMap<String, Connection> connections = getAllConnections();
    	for (Connection connection : connections.values())
    		connection.setWeight(Constants.ConnectionKilled);
    }
    @Override
    public void killFluxes(){
    	HashMap<String, Connection> connections = getAllConnections();
    	for (Connection connection : connections.values()){
    		connection.setCapacity(Constants.FluxConnKilled);
    		connection.setFlux(Constants.FluxConnKilled);
    		
    		if(connection.getWeight() == Constants.ConnectionKilled) 
    			connection.setWeight(0);
    	}
    }
    
	@Override
	public void transpose() {
		GraphImpl aux = new GraphImpl();
		for(Node node : nodes){
			aux.add(node.getPos(),node.getTag());
			
			ArrayList<Node> nodeList = aux.getNodes(); 
			Node n = nodeList.get(nodeList.size()-1);
			copyNode(node,n);
   
		}
		
		for(Node node : nodes){
			int id1 = node.getNumberId();

			for(Connection connection : node.getConnections()){
				Node other = connection.getOtherNode();
				int id2 = other.getNumberId();				
				aux.ConnectNodes(id2, id1, connection.getWeight());

                Node node1 = getNodes().get(id1);
                Node node2 = getNodes().get(id2);

				Connection conn = node1.getConnectionTo(node2);
                conn.setCapacity(connection.getCapacity());
                conn.setFlux(connection.getFlux());
			
			}
		}
		
		replace(aux);
	}

}
